home *** CD-ROM | disk | FTP | other *** search
Wrap
//============================================================================== // THRDOMTR.CPP : Main source for THRDOMTR.EXE 'Thread-o-meter' multi-threading // utility for use with Windows 95 and Windows NT. // // (Shows off Win32 multi-threading including some synchronization API.) // // v. 1.00 // // 'What's the Code?', September 1995, Computer Shopper. // // Compiler: Microsoft Visual C++ 2,x -- (Should compile // with any compiler licensing MFC 3.0 or greater) // // // by Richard Dragan (CIS) 74743,1510 // (WWW) 74743.1510@compuserve.com // //============================================================================== #include <process.h> #include <stdio.h> #include <stdlib.h> #include "stdafx.h" #include "resource.h" #include "thrdtsts.h" #include "fpcalc.h" #include "thrdoptn.h" #include "fileopts.h" #include "thrdomtr.h" // Used for data to write for file I/O demo. char achGlobalBuff[BUFF_SIZE]; char szGlobalMutexName[] = "ThreadCollectionMutex"; // Accessed by competing threads. CRITICAL_SECTION CriticalSectionObj; BOOL bKillingAllThreads = FALSE; // These two globals are accessed by all threads in the thread demo. // Coordinated access (mutual exclusion) is provided by the mutex object -OR- // critical sections, depending on which settings you //------------------------------------------------------------------------------ DWORD dwGlobalThreadLastTimeStamp = 0L; // Contains timestamp (in system // ticks) of last thread to finish. int nGlobalThreadFinishedCount = 0L; // A count of finished threads. //------------------------------------------------------------------------------ HINSTANCE hInst = NULL; // Dialog box procedure used by thread-in-a-window tests. BOOL CALLBACK ThreadDlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { int nId = LOWORD(wParam); char szCaption[40]; switch(uMsg) { case PM_START_THREAD_PROCESSING: // Signal we should start processing. wsprintf(szCaption, "Thread #%d", (int)LOWORD(lParam)); SetWindowText(hdlg, szCaption); if(wParam == 1) { SetDlgItemText(hdlg, IDC_THRD_WIN_PROMPT, "Calculating 1000 Primes..."); UpdateWindow(GetDlgItem(hdlg, IDC_THRD_WIN_PROMPT)); } else if(wParam == 2) { SetDlgItemText(hdlg, IDC_THRD_WIN_PROMPT, "Floating point calculations..."); UpdateWindow(GetDlgItem(hdlg, IDC_THRD_WIN_PROMPT)); } else if(wParam == 3) { // Timed graphics. // Hide our static prompt. // Re-init. random generator (otherwise, windows show the same stuff.) srand((UINT)GetTickCount()); ShowWindow(GetDlgItem(hdlg, IDC_THRD_WIN_PROMPT), SW_HIDE); } else // Untimed graphics. { // Hide our static prompt. srand((UINT)GetTickCount()); ShowWindow(GetDlgItem(hdlg, IDC_THRD_WIN_PROMPT), SW_HIDE); } return TRUE; case WM_COMMAND: switch(nId) { case IDOK: case IDCANCEL: DestroyWindow(hdlg); default: return FALSE; } case WM_CLOSE: DestroyWindow(hdlg); return TRUE; } return FALSE; } void FileWriteThreadProc(void *pParams) { FILETHREADPARAMS *pThrdParams = (FILETHREADPARAMS *)pParams; ASSERT (pParams); // Make copy of our params. so we are re-entrant. FILETHREADPARAMS MyParams; memcpy(&MyParams, pThrdParams, sizeof(FILETHREADPARAMS)); const int UPDATE_PROGRESS = 6; // Every 6 x 4KB = 24KB, update status bar. int nProgressCount = 0; char szMsg[256]; float fPercentDone; // Write specified # of bytes to a temporary file. DWORD dwTotalBytesDone = 0L; DWORD dwBytesToGo = 0L; WORD uBytesToWrite = 0L; DWORD dwBytesThisPass = 0L; HFILE hFileOut = _lcreat(MyParams.szFileName, 0); if(hFileOut == HFILE_ERROR) { wsprintf(szMsg, "Can't open file '%s' for writing.", MyParams.szFileName); MessageBox(NULL, szMsg, "Can't Create File", MB_ICONSTOP); if(MyParams.bIsAsync) { ::SendMessage(MyParams.hwndParent, PM_FILE_THREAD_ENDED, 0, FALSE); _endthread(); } return; } // To here we have an open file. Write required number of bytes. // Update status bar in main window with percentage of save. while(1) { dwBytesToGo = MyParams.dwTotalBytes - dwTotalBytesDone; // Write contents of entire buffer unless we're on last pass. if(dwBytesToGo < BUFF_SIZE) uBytesToWrite = (UINT)dwBytesToGo; else uBytesToWrite = BUFF_SIZE; dwBytesThisPass = (DWORD)_lwrite(hFileOut, achGlobalBuff, uBytesToWrite); if(dwBytesThisPass != uBytesToWrite) { MessageBox(NULL, "The requested number of bytes could not be written to temp. file. Ensure that you have enough free space on the C: drive for this demo. and try again.", "Can't Write To File", MB_ICONSTOP); // Delete our file. _lclose(hFileOut); _unlink(MyParams.szFileName); if(MyParams.bIsAsync) _endthread(); return; } dwTotalBytesDone += dwBytesThisPass; if(dwTotalBytesDone >= MyParams.dwTotalBytes) // We're finished. break; nProgressCount++; if(nProgressCount >= UPDATE_PROGRESS); { // Then update our progress bar in parent window. fPercentDone = (float)dwTotalBytesDone/(float)MyParams.dwTotalBytes * (float)100.0; sprintf(szMsg, "Writing %u bytes (%3.1f%% done)...", MyParams.dwTotalBytes, fPercentDone); ::SendMessage(MyParams.hwndParent, PM_THREAD_STATUS_MSG, 0, (LPARAM)szMsg); nProgressCount = 0; // Reset counter. } } ::SendMessage(MyParams.hwndParent, PM_THREAD_STATUS_MSG, 0, (LPARAM)"(Data was written successfully.)"); _lclose(hFileOut); _unlink(MyParams.szFileName); if(MyParams.bIsAsync) { ::SendMessage(MyParams.hwndParent, PM_FILE_THREAD_ENDED, 0, TRUE); _endthread(); // Success } } void FileReadThreadProc(void *pParams) { FILETHREADPARAMS *pThrdParams = (FILETHREADPARAMS *)pParams; ASSERT (pParams); char szMsg[256]; // Make copy of our params. so we are re-entrant. FILETHREADPARAMS MyParams; lstrcpy(MyParams.szFileName, pThrdParams->szFileName); MyParams.hwndParent = pThrdParams->hwndParent; MyParams.bIsAsync = pThrdParams->bIsAsync; HFILE hFileIn = _lopen(MyParams.szFileName, OF_READ); if(hFileIn == HFILE_ERROR) { wsprintf(szMsg, "Can't open file '%s' for reading. Make sure this path is correct and that is not in-use by another application and try again.", MyParams.szFileName); MessageBox(NULL, szMsg, "Can't Open File", MB_ICONSTOP); if(MyParams.bIsAsync) { ::SendMessage(MyParams.hwndParent, PM_FILE_THREAD_ENDED, 0, FALSE); _endthread(); } return; } // Otherwise, get size of this file. MyParams.dwTotalBytes = (DWORD) _llseek(hFileIn, 0, FILE_END); _llseek(hFileIn, 0, FILE_BEGIN); const int UPDATE_PROGRESS = 4; // Every 4 x 4KB = 16KB, update status bar. int nProgressCount = 0; float fPercentDone; // Write specified # of bytes to a temporary file. DWORD dwTotalBytesDone = 0L; DWORD dwBytesToGo = 0L; WORD uBytesToRead = 0L; DWORD dwBytesThisPass = 0L; // To here we have an open file. Read in all of this file. // Update status bar in main window with percentage of read. while(1) { dwBytesToGo = MyParams.dwTotalBytes - dwTotalBytesDone; // Read into entire buffer unless we're on last pass. if(dwBytesToGo < BUFF_SIZE) uBytesToRead = (UINT)dwBytesToGo; else uBytesToRead = BUFF_SIZE; dwBytesThisPass = (DWORD)_lread(hFileIn, achGlobalBuff, uBytesToRead); if(dwBytesThisPass != uBytesToRead) { MessageBox(NULL, "The requested number of bytes could not be read.", "Can't Read From File", MB_ICONSTOP); // Delete our file. _lclose(hFileIn); if(MyParams.bIsAsync) _endthread(); return; } dwTotalBytesDone += dwBytesThisPass; if(dwTotalBytesDone >= MyParams.dwTotalBytes) // We're finished. break; nProgressCount++; if(nProgressCount >= UPDATE_PROGRESS); { // Then update our progress bar in parent window. fPercentDone = (float)dwTotalBytesDone/(float)MyParams.dwTotalBytes * (float)100.0; sprintf(szMsg, "Reading %u bytes (%3.1f%% done)...", MyParams.dwTotalBytes, fPercentDone); ::SendMessage(MyParams.hwndParent, PM_THREAD_STATUS_MSG, 0, (LPARAM)szMsg); nProgressCount = 0; // Reset counter. } } ::SendMessage(MyParams.hwndParent, PM_THREAD_STATUS_MSG, 0, (LPARAM)"(Data was read successfully.)"); _lclose(hFileIn); if(MyParams.bIsAsync) { ::SendMessage(MyParams.hwndParent, PM_FILE_THREAD_ENDED, 0, TRUE); _endthread(); // Success } } void ThreadDemoProc(void *pParams) { THREADPARAMS *pThrdParams = (THREADPARAMS *)pParams; HWND hwndMyThrd = NULL; THREADPARAMS MyParams; char szMsg[256]; HANDLE hMutex = 0; // Make a copy of of params. so that we're guaranteed to be reentry. memcpy(&MyParams, pThrdParams, sizeof(THREADPARAMS)); // Free up this thread block. We've copied it locally. HGLOBAL hGlobal = GlobalHandle(pParams); GlobalUnlock(hGlobal); GlobalFree(hGlobal); if(MyParams.bUseMutex) { hMutex = OpenMutex(SYNCHRONIZE, TRUE, szGlobalMutexName); if(!hMutex) { wsprintf(szMsg, "Can't open mutex in Thread #%d. 'Critical sections' will be used instead", MyParams.nThreadNum); MessageBox(NULL, szMsg, "Can't open mutex.", MB_ICONSTOP); MyParams.bUseMutex = FALSE; } } if(MyParams.bUseWindow) { hwndMyThrd = CreateDialog(hInst, MAKEINTRESOURCE(IDD_THREAD_WINDOW), MyParams.hwndParent, (DLGPROC)ThreadDlgProc); if(!hwndMyThrd) { // Can't do this--we need a window to run graphics tests. MessageBox(NULL, "Unable to create thread window.", "Can't Create Window", MB_ICONSTOP); _endthread(); return; } RECT rWin; GetWindowRect(hwndMyThrd, &rWin); MoveWindow(hwndMyThrd, MyParams.ptWinStart.x, MyParams.ptWinStart.y, rWin.right - rWin.left, rWin.bottom - rWin.top, TRUE); ShowWindow(hwndMyThrd, SW_SHOWNORMAL); UpdateWindow(hwndMyThrd); SendMessage(hwndMyThrd, PM_START_THREAD_PROCESSING, (WPARAM)MyParams.nDemoType, (LPARAM)MyParams.nThreadNum); UpdateWindow(hwndMyThrd); } LONG *pPrimesArray = NULL; UINT uPrimesTotal = 0; UINT uFloatingPtReps = 0; UINT uGraphicsReps = 0; // Run requested type of test with or without windows. if(MyParams.nDemoType == 1) { // 'Primes' requires a little additional initialization. pPrimesArray = new long[1000]; if(!pPrimesArray) { MessageBox(NULL, "Not enough memory for Primes test!.", "Not Enough Memory", MB_ICONSTOP); if(hwndMyThrd) { DestroyWindow(hwndMyThrd); UpdateWindow(hwndMyThrd); } _endthread(); return; } } // Loop here. (We break tests up into smaller pieces for better // distribution of CPU timeslices.) BOOL bContinue = TRUE; while(!bKillingAllThreads && bContinue) { MSG msg; if(PeekMessage(&msg, 0, WM_KEYDOWN, WM_KEYDOWN, PM_NOREMOVE)) { // 'ESC' to abort demo. if(msg.wParam == VK_ESCAPE && !bKillingAllThreads) { // If 'escape' is pressed, end this demo. from parent window. SendMessage(MyParams.hwndParent, WM_COMMAND, (WPARAM)ID_THREAD_DEMO, (LPARAM)0L); continue; } } switch(MyParams.nDemoType) { case 1: // Primes (1000 primes calculated per thread) uPrimesTotal = DoPrimesCalc(1000, 10, pPrimesArray, uPrimesTotal); if(uPrimesTotal >= 1000) { // Check that we did all our work. ASSERT (pPrimesArray[999] == 7919); bContinue = FALSE; } break; case 2: // Floating Point (20 Repetitions per thread) uFloatingPtReps = DoFloatingPointCalc(20, 2, uFloatingPtReps); if(uFloatingPtReps >= 20) bContinue = FALSE; break; case 3: // Graphics (500 screen draws per thread) uGraphicsReps = DoGraphics(hwndMyThrd, 500, 8, uGraphicsReps); if(uGraphicsReps >= 500) bContinue = FALSE; break; case 4: // Graphics demo. (Continue until we're stopped by user.) DoGraphics(hwndMyThrd, 0, 10, 0); break; } } if(MyParams.nDemoType == 1) { // Clean-up primes array. delete[] pPrimesArray; } // Shut down our window, if one was created. if(hwndMyThrd) { DestroyWindow(hwndMyThrd); UpdateWindow(hwndMyThrd); } // Now update global with our ending timestamp. Use mutex object or critical // section as specified by user. if(!bKillingAllThreads) { if(MyParams.bUseMutex && hMutex) { // Wait for mutex. Write to global memory when we have clearance. WaitForSingleObject(hMutex, INFINITE); dwGlobalThreadLastTimeStamp = GetTickCount(); nGlobalThreadFinishedCount++; ReleaseMutex(hMutex); } else { // Wait for a critical section. (The 'critical' code between these two calls // will be run exclusively by the scheduler, guaranteeing mutual exclusion.) EnterCriticalSection(&CriticalSectionObj); dwGlobalThreadLastTimeStamp = GetTickCount(); nGlobalThreadFinishedCount++; LeaveCriticalSection(&CriticalSectionObj); } } _endthread(); } ///////////////////////////////////////////////////////////////////////////// // theApp: // Just creating this application object runs the whole application. // CTheApp NEAR theApp; ///////////////////////////////////////////////////////////////////////////// // CMainWindow constructor: // Create the window with the appropriate style, size, menu, etc. // CMainWindow::CMainWindow() { cxTextWidth = NULL; cyTextHeight = NULL; // Load our thread dialog to get its dimensions. HWND hwndMyThrd = CreateDialog(hInst, MAKEINTRESOURCE(IDD_THREAD_WINDOW), m_hWnd, (DLGPROC)ThreadDlgProc); xThrdWnd = 10; yThrdWnd = 10; if(hwndMyThrd) { RECT rThrdWin; ::GetWindowRect(hwndMyThrd, &rThrdWin); cxThrdWnd = rThrdWin.right - rThrdWin.left; cyThrdWnd = rThrdWin.bottom - rThrdWin.top; ::DestroyWindow(hwndMyThrd); } else { // Initialize coordinates for thread windows. cxThrdWnd = 100; cyThrdWnd = 50; } // Create mutex object for later. hMutex = CreateMutex((LPSECURITY_ATTRIBUTES)NULL, FALSE, szGlobalMutexName); if(!hMutex) MessageBox("Unable to create mutex object. You will have to use 'critical sections' for the thread demo.", "Can't Create Mutex Object", MB_ICONSTOP); // Prepare for the use of a 'critical section' by multiple threads. InitializeCriticalSection(&CriticalSectionObj); // Initialize our class members to test defaults. bFileIODemoStarted = FALSE; strWorkFileName = "C:\\TEST.DAT"; // File for read/write test. bDoAsynchronousFileIO = TRUE; // False for synchronous. bDoFileWrite = TRUE; // FALSE = Do Read uFileKB = 500; bThreadDemoStarted = FALSE; nThreadCountActual = 0; cxScreen = GetSystemMetrics(SM_CXSCREEN); cyScreen = GetSystemMetrics(SM_CYSCREEN); // Elapsed time. dwTimeStart = 0; dwTimeStop = 0; // For thread demo. nThreadCount = 4; nTestType = 1; // dwThreadPriorityClass = NORMAL_PRIORITY_CLASS; bUseMutexObjects = TRUE; // FALSE = use Critical Sections. bUseWindowPerThread = TRUE; // For both. bLogOutput = FALSE; hfLogFile = HFILE_ERROR; strLogFilename = "C:\\THRDLOG.TXT"; // Simple demo. error tracking. nErrCode = 0; sErrMsg = ""; srand(1234); // Same seed = same byte info. for file write. for(long i=0; i < BUFF_SIZE; i++) achGlobalBuff[i] = rand() % 256; LoadFrame(IDR_MAINFRAME); } // OnAbout: Display About Dialog box. void CMainWindow::OnAbout() { CDialog AboutDlg(IDD_ABOUTBOX); AboutDlg.DoModal(); } // CMainWindow message map: // Associate messages with member functions. // BEGIN_MESSAGE_MAP( CMainWindow, CFrameWnd ) //{{AFX_MSG_MAP( CMainWindow ) ON_COMMAND(ID_APP_ABOUT, OnAbout) ON_COMMAND(ID_THREAD_DEMO_OPTIONS, OnThreadDemoOptions) ON_COMMAND(ID_FILE_IO_OPTIONS, OnFileIOOptions) ON_WM_PAINT() ON_WM_SIZE() ON_WM_CREATE() ON_COMMAND(ID_FILE_IO_DEMO, OnDoFileIODemo) ON_COMMAND(ID_THREAD_DEMO, OnDoThreadDemo) ON_UPDATE_COMMAND_UI(ID_FILE_IO_DEMO, OnUpdateFileIODemo) ON_MESSAGE(PM_THREAD_STATUS_MSG, OnThreadStatusMsg) ON_MESSAGE(PM_FILE_THREAD_ENDED, OnFileThreadEnded) ON_UPDATE_COMMAND_UI(ID_FILE_IO_OPTIONS, OnUpdateFileIOOptions) ON_UPDATE_COMMAND_UI(ID_THREAD_DEMO, OnUpdateThreadDemo) ON_UPDATE_COMMAND_UI(ID_THREAD_DEMO_OPTIONS, OnUpdateThreadDemoOptions) ON_WM_TIMER() ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTheApp // InitInstance: // When any CTheApp object is created, this member function is automatically // called. Any data may be set up at this point. // // Also, the main window of the application should be created and shown here. // Return TRUE if the initialization is successful. // BOOL CTheApp::InitInstance() { TRACE0("CTheApp::InitInstance\n"); Enable3dControls(); // use 3d controls in dialogs // Save handle to our instance. hInst = m_hInstance; m_pMainWnd = new CMainWindow; m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } void CMainWindow::OnThreadDemoOptions() { // Change Thread Demo. Options. CThreadOptionsDlg MyThreadOptionsDlg; // Load current settings to dialog box variables. MyThreadOptionsDlg.m_nThreadCount = nThreadCount; MyThreadOptionsDlg.m_nTestType = nTestType; MyThreadOptionsDlg.m_dwThreadPriorityClass = dwThreadPriorityClass; MyThreadOptionsDlg.m_bUseMutexObjects = bUseMutexObjects; MyThreadOptionsDlg.m_bUseWindowPerThread = bUseWindowPerThread; MyThreadOptionsDlg.m_bLogOutput = bLogOutput; MyThreadOptionsDlg.m_strLogFilename = strLogFilename; int rc = MyThreadOptionsDlg.DoModal(); if(rc) { // Then save new settings to our class members. nThreadCount = MyThreadOptionsDlg.m_nThreadCount; nTestType = MyThreadOptionsDlg.m_nTestType; dwThreadPriorityClass = MyThreadOptionsDlg.m_dwThreadPriorityClass; bUseMutexObjects = MyThreadOptionsDlg.m_bUseMutexObjects; bUseWindowPerThread = MyThreadOptionsDlg.m_bUseWindowPerThread; bLogOutput = MyThreadOptionsDlg.m_bLogOutput; strLogFilename = MyThreadOptionsDlg.m_strLogFilename; } } void CMainWindow::OnFileIOOptions() { // Change Thread Demo. Options. CFileDemoOptionsDlg MyFileDemoOptionsDlg; // Load current settings to dialog box variables. MyFileDemoOptionsDlg.m_strWorkFileName = strWorkFileName; MyFileDemoOptionsDlg.m_bDoAsynchronousFileIO = bDoAsynchronousFileIO; MyFileDemoOptionsDlg.m_bDoFileWrite = bDoFileWrite; MyFileDemoOptionsDlg.m_uFileKB = uFileKB; int rc = MyFileDemoOptionsDlg.DoModal(); if(rc) { // Then save new settings to our class members. strWorkFileName = MyFileDemoOptionsDlg.m_strWorkFileName; bDoAsynchronousFileIO = MyFileDemoOptionsDlg.m_bDoAsynchronousFileIO; bDoFileWrite = MyFileDemoOptionsDlg.m_bDoFileWrite; uFileKB = MyFileDemoOptionsDlg.m_uFileKB; } } void CMainWindow::OnPaint() { CPaintDC dc(this); // device context for painting // Gray background. RECT r; GetClientRect(&r); dc.SelectStockObject(NULL_PEN); dc.SelectStockObject(LTGRAY_BRUSH); dc.Rectangle(0, 0, r.right + 1, r.bottom + 1); // Do not call CFrameWnd ::OnPaint() for painting messages } void CMainWindow::OnSize(UINT nType, int cx, int cy) { CFrameWnd ::OnSize(nType, cx, cy); // Re-size our label and listbox controls. RECT rStatusLB = { cyTextHeight / 2, cyTextHeight * 2, cx - (cyTextHeight / 2), cy - (cyTextHeight * 2) }; StatusLB.MoveWindow(&rStatusLB); RECT rStatusLBLabel = { cyTextHeight / 2, cyTextHeight / 2, cx - (cyTextHeight / 2), (cyTextHeight * 3) / 2 }; StatusLBLabel.MoveWindow(&rStatusLBLabel); // Scroll if we can't display at least 70 characters. StatusLB.SetHorizontalExtent(70 * cxTextWidth); if(cx < cxTextWidth * 35) { // If we're too small, don't show time in status bar. StatusBar.SetPaneInfo( 0, 0, SBPS_NORMAL, cx - cxTextWidth); StatusBar.SetPaneInfo( 1, 0, SBPS_NORMAL, 0); } else { StatusBar.SetPaneInfo( 0, 0, SBPS_NORMAL, cx - (cxTextWidth * 20)); StatusBar.SetPaneInfo( 1, 0, SBPS_NORMAL, cxTextWidth * 20); } } int CMainWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd ::OnCreate(lpCreateStruct) == -1) return -1; // Get our system font size information. TEXTMETRIC tm; CClientDC dc(this); dc.GetTextMetrics(&tm); cyTextHeight = tm.tmHeight; cxTextWidth = tm.tmAveCharWidth; RECT r = { 0, 0, 0, 0 }; // No size -- wait til WM_SIZE to re-size. // Initialize. our controls that appear in frame window. StatusLBLabel.Create("Output :", WS_VISIBLE | SS_LEFT | WS_CHILD, r, this, (unsigned int)IDC_STATIC); StatusLB.Create(WS_VISIBLE | WS_BORDER | WS_CHILD | WS_VSCROLL | WS_HSCROLL, r, this, IDC_MSG_LB); StatusBar.Create(this); UINT uIDs[2] = { 0, IDC_TIME }; StatusBar.SetIndicators(uIDs, 2); return 0; } void CMainWindow::OnDoFileIODemo() { char szTemp[256]; CString strFileName; static FILETHREADPARAMS FileThrdParams; // Do File I/O demo. with current options. bFileIODemoStarted = FALSE; // strWorkFileName; // File for read/write test. // bDoAsychronousFileIO; // False for synchronous. FileThrdParams.hwndParent = m_hWnd; if(bDoFileWrite) { szTemp[256]; if(tmpnam(szTemp) == NULL) { MessageBox("Unable to create temp. file for writing.", "Can't Create Temp. File", MB_ICONSTOP); return; } strFileName = "C:"; strFileName += szTemp; strFileName += "dat"; lstrcpy(FileThrdParams.szFileName, strFileName.GetBuffer(256)); FileThrdParams.dwTotalBytes = (DWORD)uFileKB * 1024L; // Do a file write. if(bDoAsynchronousFileIO) { wsprintf(szTemp, "Starting File I/O -- Asynchronous Write:"); AddMsgToLB(szTemp); wsprintf(szTemp, " %u bytes to '%s'.", FileThrdParams.dwTotalBytes, FileThrdParams.szFileName); AddMsgToLB(szTemp); FileThrdParams.bIsAsync = TRUE; ::SetCursor(::LoadCursor(NULL, IDC_APPSTARTING)); hThrdFileDemo = (HANDLE)_beginthread(FileWriteThreadProc, 16384, (void *)&FileThrdParams); if(hThrdFileDemo == (HANDLE)-1) { ::SetCursor(::LoadCursor(NULL, IDC_APPSTARTING)); MessageBox("Unable to create thread for file write.", "Can't Create Thread", MB_ICONSTOP); return; } else bFileIODemoStarted = TRUE; } else { wsprintf(szTemp, "Starting File I/O -- Synchronous Write:"); AddMsgToLB(szTemp); wsprintf(szTemp, " %u bytes to '%s'.", FileThrdParams.dwTotalBytes, FileThrdParams.szFileName); AddMsgToLB(szTemp); // Just call thread procedure directly. FileThrdParams.bIsAsync = FALSE; // Hourglass cursor --just like Windows 3.11 ::SetCursor(::LoadCursor(NULL, IDC_WAIT)); FileWriteThreadProc(&FileThrdParams); AddMsgToLB("File I/O Demo ended."); ::SetCursor(LoadCursor(NULL, IDC_ARROW)); } } else { lstrcpy(FileThrdParams.szFileName, strWorkFileName); // FALSE = Do Read with provided name. if(bDoAsynchronousFileIO) { wsprintf(szTemp, "Starting File I/O -- Asynchronous Read:"); AddMsgToLB(szTemp); wsprintf(szTemp, " file '%s'.", FileThrdParams.szFileName); AddMsgToLB(szTemp); FileThrdParams.bIsAsync = TRUE; hThrdFileDemo = (HANDLE)_beginthread(FileReadThreadProc, 4096, (void *)&FileThrdParams); if(hThrdFileDemo == (HANDLE)-1) { MessageBox("Unable to create thread for file read.", "Can't Create Thread", MB_ICONSTOP); return; } else bFileIODemoStarted = TRUE; } else { wsprintf(szTemp, "Starting File I/O -- Synchronous Read:"); AddMsgToLB(szTemp); wsprintf(szTemp, " file '%s'.", FileThrdParams.szFileName); AddMsgToLB(szTemp); // Just call thread procedure directly. FileThrdParams.bIsAsync = FALSE; // Hourglass cursor --just like Windows 3.11 ::SetCursor(::LoadCursor(NULL, IDC_WAIT)); FileReadThreadProc(&FileThrdParams); AddMsgToLB("File I/O Demo ended."); ::SetCursor(LoadCursor(NULL, IDC_ARROW)); } } } void CMainWindow::OnDoThreadDemo() { static THREADPARAMS ThrdParams; int rc; int i; char szMsg[256]; CString strMsg; if(bThreadDemoStarted) { bKillingAllThreads = TRUE; // Setting this flags helps our threads stop processing. // If demo. is already running, kill off all our threads! for(int i = 0; i < 64; i++) if(hThrd[i] != (HANDLE)-1) CloseHandle(hThrd[i]); AddMsgToLB("Thread Demo. stopped by user."); bThreadDemoStarted = FALSE; KillTimer(5555); SetTimeMsg(""); // Restore process priority to 'normal.' HANDLE hMyProcess = GetCurrentProcess(); ASSERT (hMyProcess); rc = SetPriorityClass(hMyProcess, NORMAL_PRIORITY_CLASS); if(!rc) { MessageBox("Unable to restore thread priority class. Threads will run in the default 'normal' priority class.", "Can't Restore Thread Priority", MB_ICONEXCLAMATION); } if(hfLogFile != HFILE_ERROR) { _lclose(hfLogFile); hfLogFile = HFILE_ERROR; } return; } bKillingAllThreads = FALSE; if(bLogOutput) { if((hfLogFile = _lopen(strLogFilename.GetBuffer(256), OF_WRITE)) != HFILE_ERROR) { // If file exists, append to end of file. _llseek(hfLogFile, 0, FILE_END); } else { // Try to create new file, if none exists. hfLogFile = _lcreat(strLogFilename.GetBuffer(256), 0); } if(hfLogFile == HFILE_ERROR) { wsprintf(szMsg, "Can't open log file '%s'. Output will not be saved.", strLogFilename.GetBuffer(256)); MessageBox(szMsg, "Can't Open Log File", MB_ICONEXCLAMATION); } } xThrdWnd = 10; yThrdWnd = 10; nThreadCountActual = 0; ThrdParams.nDemoType = nTestType; if(nTestType == 4) // All demo. tests are timed except untimed graphics. ThrdParams.bIsTimed = FALSE; else ThrdParams.bIsTimed = TRUE; ThrdParams.bUseWindow = bUseWindowPerThread; ThrdParams.hwndParent = m_hWnd; ThrdParams.bUseMutex = bUseMutexObjects; // Clear thread handle array. memset(hThrd, -1, sizeof(HANDLE) * 64); HANDLE hMyProcess = GetCurrentProcess(); ASSERT (hMyProcess); // This is also a way to get the current process handle. // DWORD ProcessId = GetCurrentProcessId(); // HANDLE hMyProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessId); // Reset globals used to collect info. from individual threads. dwGlobalThreadLastTimeStamp = 0L; nGlobalThreadFinishedCount = 0L; // Start timing here! if(ThrdParams.bIsTimed) dwTimeStart = GetTickCount(); DWORD dwStackSize = 8192; for(i = 0; i < nThreadCount; i++) { if(ThrdParams.bUseWindow) { ThrdParams.ptWinStart.x = xThrdWnd; ThrdParams.ptWinStart.y = yThrdWnd; } else { ThrdParams.ptWinStart.x = -1; ThrdParams.ptWinStart.y = -1; } // Create each thread with desired priority class. ThrdParams.nThreadNum = i + 1; // Pass our thread param. as a global object. // (This ensures that threads find the right params.) HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(ThrdParams)); LPSTR pThrdParamsGlobalBlock = (LPSTR)GlobalLock(hGlobal); if(!pThrdParamsGlobalBlock) MessageBox("Can't allocate memory to initialize thread.", "Not Enough Memory", MB_ICONSTOP); else { memcpy(pThrdParamsGlobalBlock, &ThrdParams, sizeof(ThrdParams)); hThrd[i] = (HANDLE)_beginthread(ThreadDemoProc, dwStackSize, (void *)pThrdParamsGlobalBlock); } if(hThrd[i] == (HANDLE)-1) { MessageBox("Unable to create thread #%d for demo. Try running demo with fewer threads or reduce system memory usage and then try again.", "Can't Create Thread", MB_ICONSTOP); return; } else { SuspendThread(hThrd[i]); bThreadDemoStarted = TRUE; nThreadCountActual++; if(ThrdParams.bUseWindow) { yThrdWnd += cyThrdWnd + 1; // Next thread window would appear here. if(yThrdWnd + cyThrdWnd > cyScreen) { yThrdWnd = 10; xThrdWnd += cxThrdWnd + 1; if(xThrdWnd + cxThrdWnd > cxScreen) { // Windows will 'tile' at beginning of screen after lower left corner // has been reached. xThrdWnd = 10; yThrdWnd = 10; } } } } } rc = SetPriorityClass(hMyProcess, dwThreadPriorityClass); if(!rc) { MessageBox("Unable to change thread priority class. Threads will run in the default 'normal' priority class.", "Can't Set Thread Priorities", MB_ICONEXCLAMATION); } // Resume all threads. for(i = 0; i < 64; i++) { if(hThrd[i] != (HANDLE)-1) ResumeThread(hThrd[i]); } if(nThreadCountActual == 0 && !bThreadDemoStarted) { AddMsgToLB("No threads were created! Demo stopped."); return; } if(ThrdParams.nDemoType == 1) strMsg = "Starting Thread Demo. -- 1000 Primes with "; else if(ThrdParams.nDemoType == 2) strMsg = "Starting Thread Demo. -- Floating-Point Calculation with "; else if(ThrdParams.nDemoType == 3) strMsg = "Starting Thread Demo. -- Timed Graphics with "; else // default is untimed graphics. strMsg = "Starting Thread Demo. -- Untimed Graphics with "; if(nThreadCountActual == 1) wsprintf(szMsg, "1 thread %s", ThrdParams.bUseWindow ? "(windowed)." : "."); else wsprintf(szMsg, "%d threads %s", nThreadCountActual, ThrdParams.bUseWindow ? "(windowed)." : "."); strMsg += szMsg; AddMsgToLB(strMsg); if(ThrdParams.bIsTimed) { // If we're timed, start a timer which will check on the // status of each thread. The granularity here is larger (.900 sec.) // than what we'll get from each thread. (As each thread exits, // it will write its time to the global thread variables in millisec.); SetTimer(5555, 900, NULL); } } LRESULT CMainWindow::OnThreadStatusMsg(WPARAM uUnused, LPARAM lpszMsg) { // Process status message from our thread. StatusBar.SetWindowText((LPSTR)lpszMsg); StatusBar.UpdateWindow(); return 0L; } LRESULT CMainWindow::OnFileThreadEnded(WPARAM uUnused, LPARAM lResult) { if(bFileIODemoStarted) { if(lResult) AddMsgToLB("File I/O Demo completed OK."); else AddMsgToLB("File I/O Demo ended with an error."); } bFileIODemoStarted = FALSE; return 0L; } void CMainWindow::OnUpdateFileIOOptions(CCmdUI* pCmdUI) { if(!bFileIODemoStarted && !bThreadDemoStarted) pCmdUI->Enable(TRUE); else pCmdUI->Enable(FALSE); } void CMainWindow::OnUpdateThreadDemoOptions(CCmdUI* pCmdUI) { if(!bFileIODemoStarted && !bThreadDemoStarted) pCmdUI->Enable(TRUE); else pCmdUI->Enable(FALSE); } void CMainWindow::OnUpdateFileIODemo(CCmdUI* pCmdUI) { if(!bFileIODemoStarted && !bThreadDemoStarted) pCmdUI->Enable(TRUE); else pCmdUI->Enable(FALSE); } void CMainWindow::OnUpdateThreadDemo(CCmdUI* pCmdUI) { if(!bFileIODemoStarted) { if(bThreadDemoStarted) { // Change text to stop demo. pCmdUI->SetText("Stop Thread Demo."); } else // Change text to stop demo. pCmdUI->SetText("Start Thread Demo."); pCmdUI->Enable(TRUE); } else pCmdUI->Enable(FALSE); } LPSTR CMainWindow::GetElapsedTimeAsStr(DWORD dwEarlier, DWORD dwLater) { static char szTime[80]; DWORD dwElapsed = dwLater - dwEarlier; int Min = dwElapsed / 60000; int Hrs = Min / 60; Min = Min % 60; float Sec = (float)(dwElapsed % 60000); Sec /= (float)1000.0; sprintf(szTime, "%02d:%02d:%05.2f", Hrs, Min, Sec); return szTime; } void CMainWindow::ShowElapsedTime(DWORD dwEarlier, DWORD dwLater) { LPSTR lpszTime = GetElapsedTimeAsStr(dwEarlier, dwLater); SetTimeMsg(lpszTime); } void CMainWindow::OnTimer(UINT nIDEvent) { DWORD dwNow; char szMsg[256]; int nCompletedThreads = 0; if(nIDEvent == 5555) { // Update time in status bar. if(bUseMutexObjects && hMutex) // Wait until we have exclusive access to this global memory location. { // Wait for mutex. Write to global memory when we have clearance. WaitForSingleObject(hMutex, INFINITE); if(nGlobalThreadFinishedCount >= nThreadCountActual) { // Then we're finished. All threads have run to completion. dwTimeStop = dwGlobalThreadLastTimeStamp; bThreadDemoStarted = FALSE; } ReleaseMutex(hMutex); } else { // Wait for a critical section. (The 'critical' code between these two calls // will be run exclusively by the scheduler, guaranteeing mutual exclusion.) EnterCriticalSection(&CriticalSectionObj); if(nGlobalThreadFinishedCount >= nThreadCountActual) { // Then we're finished. All threads have run to completion. dwTimeStop = dwGlobalThreadLastTimeStamp; bThreadDemoStarted = FALSE; } LeaveCriticalSection(&CriticalSectionObj); } if(bThreadDemoStarted) { // If we're still working, update elapsed time and thread activity. dwNow = GetTickCount(); ShowElapsedTime(dwTimeStart, dwNow); wsprintf(szMsg, "%d out of %d thread(s) still working...", nThreadCountActual - nGlobalThreadFinishedCount, nThreadCountActual); SetStatusMsg(szMsg); } else { // If we're finished, report final elapsed time and clear timer window. wsprintf(szMsg, "Test finished: %d Threads completed, Elapsed time was %s", nGlobalThreadFinishedCount, GetElapsedTimeAsStr(dwTimeStart, dwTimeStop)); AddMsgToLB(szMsg); // Stop our 'sentinel' timer as well. KillTimer(5555); SetTimeMsg(""); // Restore process priority to 'normal.' HANDLE hMyProcess = GetCurrentProcess(); ASSERT (hMyProcess); int rc = SetPriorityClass(hMyProcess, NORMAL_PRIORITY_CLASS); if(!rc) { MessageBox("Unable to restore thread priority class. Threads will run in the default 'normal' priority class.", "Can't Restore Thread Priority", MB_ICONEXCLAMATION); } if(hfLogFile) { _lclose(hfLogFile); hfLogFile = NULL; } } } else CFrameWnd ::OnTimer(nIDEvent); } void CMainWindow::AddMsgToLB(CString& strMsg, BOOL bShowInStatusBarToo) { int rc = StatusLB.AddString(strMsg); if(rc > -1) StatusLB.SetSel(rc); StatusLB.UpdateWindow(); if(bShowInStatusBarToo) { StatusBar.SetWindowText(strMsg.GetBuffer(255)); StatusBar.UpdateWindow(); } if(hfLogFile) { char szLine[300]; wsprintf(szLine, "%s\n\r", strMsg.GetBuffer(256)); _lwrite(hfLogFile, szLine, lstrlen(szLine)); } } void CMainWindow::OnDestroy() { if(hMutex) CloseHandle(hMutex); if(hfLogFile != HFILE_ERROR) _lclose(hfLogFile); CFrameWnd::OnDestroy(); }